﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sina.Weibo.Operations
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Text;

    using Sina.Weibo.Serialization;

    /// <summary>
    /// Represents a single operation that makes a web request to the API service.
    /// </summary>
    internal class ApiOperation : WebOperation
    {
        #region Class member variables

        internal const string ContentTypeFormUrlEncoded = "application/x-www-form-urlencoded;charset=UTF-8";
        internal const string AuthorizationHeader = "access_token";
        internal const string ApiClientErrorCode = "client_error";
        internal const string ApiServerErrorCode = "server_error";

        #endregion

        #region Constructors

        /// <summary>
        /// Constructs a new ApiOperation object.
        /// </summary>
        public ApiOperation(
            WeiboConnectClient client,
            Uri url,
            ApiMethod method,
            string body,
            SynchronizationContextWrapper syncContext)
            : base(url, body, syncContext)
        {
            this.Method = method;
            this.WeiboClient = client;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the reference to the WeiboConnectClient object.
        /// </summary>
        public WeiboConnectClient WeiboClient { get; private set; }

        /// <summary>
        /// Gets the API method this operation represents.
        /// </summary>
        public ApiMethod Method { get; private set; }

        /// <summary>
        /// Gets and sets the operation completed callback delegate.
        /// </summary>
        public Action<WeiboOperationResult> OperationCompletedCallback { get; set; }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Parses a string response body and creates a LiveOperationResult object from it.
        /// </summary>
        protected WeiboOperationResult CreateOperationResultForm(string responseBody)
        {
            if (string.IsNullOrEmpty(responseBody))
            {
                var error = new WeiboConnectException(
                    ApiConstants.ApiErrorCode,
                    "NoResponseData");
                return new WeiboOperationResult(error, false);
            }

            return WeiboOperationResult.FromResponse(responseBody);
        }

        /// <summary>
        /// Parses the WebResponse's body and creates a LiveOperationResult object from it.
        /// </summary>
        protected WeiboOperationResult CreateOperationResultForm(WebResponse response)
        {
            WeiboOperationResult opResult;

            bool nullResponse = (response == null);
            try
            {
                Stream responseStream = (!nullResponse) ? response.GetResponseStream() : null;
                if (nullResponse || responseStream == null)
                {
                    var error = new WeiboConnectException(
                        ApiOperation.ApiClientErrorCode,
                        "ConnectionError");

                    opResult = new WeiboOperationResult(error, false);
                }
                else
                {
                    using (var sr = new StreamReader(responseStream))
                    {
                        string rawResult = sr.ReadToEnd();
                        opResult = this.CreateOperationResultForm(rawResult);
                    }
                }
            }
            finally
            {
                if (!nullResponse)
                {
                    ((IDisposable)response).Dispose();
                }
            }

            return opResult;
        }

        /// <summary>
        /// Overwrites the base OnExecute to refresh access token if neccessary.
        /// </summary>
        protected override void OnExecute()
        {
            if (this.PrepareRequest())
            {
                try
                {
                    this.Request.BeginGetResponse(this.OnGetResponseCompleted, null);
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.RequestCanceled)
                    {
                        this.OnCancel();
                    }
                    else
                    {
                        this.OnWebResponseReceived(ex.Response);
                    }
                }
            }
        }

        /// <summary>
        /// Called when the operation is cancelled.
        /// </summary>
        protected override void OnCancel()
        {
            this.OnOperationCompleted(new WeiboOperationResult(null, true));
        }

        /// <summary>
        /// Calls the OperationCompletedCallback delegate.
        /// This method is called when the ApiOperation is completed.
        /// </summary>
        protected void OnOperationCompleted(WeiboOperationResult opResult)
        {
            Action<WeiboOperationResult> callback = this.OperationCompletedCallback;
            if (callback != null)
            {
                callback(opResult);
            }
        }

        /// <summary>
        /// Called when the operation has a WebResponse from the server to handle.
        /// </summary>
        protected override void OnWebResponseReceived(WebResponse response)
        {
            this.OnOperationCompleted(this.CreateOperationResultForm(response));
        }

        /// <summary>
        /// Prepares the web request. Sets up the correct method, headers, etc.
        /// </summary>
        protected bool PrepareRequest()
        {
            if (!this.RefreshTokenIfNeeded())
            {
                string httpMethod = this.Method.ToString().ToUpperInvariant();

                if (this.WeiboClient.Session != null)
                {
                    StringBuilder sb = new StringBuilder(this.Url.AbsoluteUri);
                    sb.Append(string.IsNullOrEmpty(this.Url.Query) ? "?" : "&");
                    sb.AppendQueryParam(AuthorizationHeader, this.WeiboClient.Session.AccessToken);

                    Uri requestUri = new Uri(sb.ToString(), UriKind.Absolute);
                    this.Request = WebRequestFactory.Current.CreateWebRequest(requestUri, httpMethod);
                }
                else
                {
                    this.Request = WebRequestFactory.Current.CreateWebRequest(this.Url, httpMethod);
                }

                if (!string.IsNullOrEmpty(this.Body))
                {
                    this.Request.ContentType = ApiOperation.ContentTypeFormUrlEncoded;
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Checks if the access token is still valid.  If not, refreshes the token.
        /// </summary>
        protected bool RefreshTokenIfNeeded()
        {
            return false;
        }

        #endregion
    }
}
